package grex;

/**
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2003</p>
 * <p>Company: </p>
 * @author not attributable
 * @version 1.0
 */
import grex.ErrorManagement.ErrorManager;
import grex.Nodes.Node;
import grex.Nodes.GeneException;
import grex.Nodes.ICategoricalOperator;
import grex.Nodes.IContinuousOperator;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

public class GPFactory implements Serializable{

	private static final long serialVersionUID = -5707054701289015863L;
	private static String MAX_CREATION_DEPTH_MARKER = "max creation depth",
            CREATION_TYPE_MARKER = "creationtype",
            PROBLEM_TYPE_MARKER = "problemtype",
            PUNISHMENT_FOR_LENGTH_MARKER = "punishment for length",
            CROSSOWER_PROBABILITY_MARKER = "crossover probabillity",
            REPRODUCTION_PROBABILITY_MARKER = "reproduction probabillity",
            MUTATION_PROBABILITY_MARKER = "mutation probabillity",
            GROW_MARKER = "grow",  VARIABLE_MARKER = "variable",
            RAMPED_HALF_AND_HALF_MARKER = "ramped half and half",
            CLASSIFICATION_MARKER = "classification",
            REGERESION_MARKER = "regresion",
            DIVIDER_MARKER = "----------BNF---------",
            FUNCTION_MARKER = "function",
            TERMINAL_MARKER = "terminal",
            TERMFUNC_MARKER = "terminalfunction",
            TERMINAL_PREDICTOR_MARKER = "terminalPredictor",
            POSDELIM = ";";
    private List bnf;
    private List vars,  functions,  terminals,  termfuncs,  sFunctions,  sTerminals;
    private Hashtable strToInt = new Hashtable(101);
    private String[] intTostr = new String[101];
    private Options ops;
    private Environment environment;
    private String predictorClass="";

    public GPFactory(String fileName, Environment environment) throws IOException, BNFException {

        this.environment = environment;
        Options ops = loadBNF(fileName);
        parseBNF();
    }

    public GPFactory(Options options, Environment environment) throws IOException, BNFException {
        this.environment = environment;
        loadBNF(options.getBNF_FILE());
        ops = options;

        parseBNF();
    }

    public Options getOptions() {
        return ops;
    }

    private Node createGene(String geneName) throws BNFException {
        try {
            Node gene = (Node) (Class.forName(Options.GENE_PACKAGE + "." + geneName).newInstance());
            gene.setOptions(ops);
            gene.setFold(ops.getTestFOLD());
            gene.setEnvironment(environment);
            return gene;
        } catch (Exception e) {
            e.printStackTrace();
            //e.printStackTrace();
            throw new BNFException(e.getMessage());
        }
    }

    public Options loadBNF(String bnfName) throws IOException, BNFException {
        /**
         * En BFN or uppbygd av en Lista av listor. Varje listas forsta element
         * inneholler vilken typ av gene det or Function/Terminal, nosta vilken
         * typ det or som integer(1) and osv det TredjeElementet inneholler en lisat
         * som beskriver genens positioner dvs kompatibla gener i positionen.
         */
        BufferedReader bnfFile;
        if (bnfName.contains("\\")) {
            bnfFile = new BufferedReader(new FileReader(bnfName));
        } else {
            bnfFile = new BufferedReader(new FileReader("." + File.separatorChar + "BNFs" + File.separator + bnfName));
        }//new InputStreamReader(getClass().getResourceAsStream("/grex/BnfFiles/" + bnfName + ".txt")));
        List<String> tmpList = new ArrayList<String>();
        String tmpLine, cs;
        /*Options ops = new Options();
        while (!(tmpLine = bnfFile.readLine()).equalsIgnoreCase(GPFactory.DIVIDER_MARKER)) {
            StringTokenizer s = new StringTokenizer(tmpLine, "=\t%");
            cs = s.nextToken();
            if (cs.equalsIgnoreCase("train percent")) {
                ops.setTEST_SIZE(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("test percent")) {
                ops.setTEST_SIZE(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("validation percent")) {
                ops.setVAL_SIZE(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("bnf file")) {
                ops.setBNF_FILE(s.nextToken());
            } else if (cs.equalsIgnoreCase("data file")) {
                ops.setDATA_FILE(s.nextToken());
            } else if (cs.equalsIgnoreCase("#generations")) {
                ops.setGENERATIONS(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("population size")) {
                ops.setPOPULATION_SIZE(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("crossover prob")) {
                ops.setCROSSOVER_PROBABILITY(Double.parseDouble(s.nextToken()));
            } else if (cs.equalsIgnoreCase("mutation prob")) {
                ops.setMUTATION_PROBABILITY(Double.parseDouble(s.nextToken()));
            } else if (cs.equalsIgnoreCase("creation depth")) {
                ops.setMAX_CREATION_DEPTH(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("length punishment")) {
                ops.setPUNISHMENT_FOR_LENGTH(Double.parseDouble(s.nextToken()));
            } else if (cs.equalsIgnoreCase("train cost")) {
                ops.setTRAIN_IMPORTENS(Double.parseDouble(s.nextToken()));
            } else if (cs.equalsIgnoreCase("validation cost")) {
                ops.setVAL_IMPORTENS(Double.parseDouble(s.nextToken()));
            } else if (cs.equalsIgnoreCase("category threshold")) {
                ops.setNR_OFF_CATEGORIAL_ITEMS(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("batch size")) {
                ops.setBATCH_SIZE(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("mofn nodes")) {
                ops.setMOFFN_USED(s.nextToken().equalsIgnoreCase("TRUE"));
            } else if (cs.equalsIgnoreCase("simplification")) {
                ops.setSIMPLIFICATION(s.nextToken().equalsIgnoreCase("TRUE"));
            } else if (cs.equalsIgnoreCase("creation type")) {
                ops.setCREATION_TYPE(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("elitism")) {
                ops.setELITISM(s.nextToken().equalsIgnoreCase("TRUE"));
            } else if (cs.equalsIgnoreCase("percistence")) {
                ops.setSTOP_WITHOUT_IMPROVMENT(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("fitness function")) {
                ops.setFITNESS_FUNCTION(s.nextToken());
            } else if (cs.equalsIgnoreCase("simplificationFunction")) {
                ops.setSIMPLIFICATION_FUNCTION(s.nextToken());
            } else if (cs.equalsIgnoreCase("tom")) {
                ops.setTOMBOLA(s.nextToken().equalsIgnoreCase("TRUE"));
            } else if (cs.equalsIgnoreCase("tpr")) {
                ops.setTOMBOLA_PERCENT(Double.parseDouble(s.nextToken()));
            } else if (cs.equalsIgnoreCase("tsl")) {
                ops.setTOMBOLA_SLOTS(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("tin")) {
                ops.setTOMBOLA_INTERVALL(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("tfi")) {
                ops.setTOMBOLA_FITNESFUNTCION(s.nextToken());
            } else if (cs.equalsIgnoreCase("simplification generations")) {
                ops.setSIMPLIFICATION_GENERATIONS(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("simplification population")) {
                ops.setSIMPLIFICATION_POPULATION(Integer.parseInt(s.nextToken()));
            } else if (cs.equalsIgnoreCase("simplification batch")) {
                ops.setSIMPLIFICATION_BATCH(Integer.parseInt(s.nextToken()));
            } else {
                throw new BNFException("Error option: " + cs + " doesn't exist");
            }
        }
        **/
        while ((tmpLine = bnfFile.readLine()) != null) {
            if(!tmpLine.equalsIgnoreCase(GPFactory.DIVIDER_MARKER))
                tmpList.add(tmpLine);
        }
        bnf = new ArrayList();
        int args, row;
        for (Iterator i = tmpList.iterator(); i.hasNext();) {
            List currentGene = new ArrayList();

            StringTokenizer pos = new StringTokenizer((String) i.next(), POSDELIM);
            String geneType = pos.nextToken();
            String geneName = pos.nextToken();
            if(geneType.equalsIgnoreCase(TERMINAL_PREDICTOR_MARKER)){
                geneType = TERMINAL_MARKER;
                predictorClass = geneName;
            }
            try {
                Node gene = null;
                if (!geneName.equalsIgnoreCase("head")) {
                    gene = (Node) (Class.forName(Options.GENE_PACKAGE + "." + geneName).newInstance());
                }
                
                if (gene == null
                        || !(gene instanceof IContinuousOperator || gene instanceof ICategoricalOperator)
                        || (gene instanceof IContinuousOperator && environment.getData().getNrOfContinousColumns() > 0)
                        || (gene instanceof ICategoricalOperator && environment.getData().getNrOfCategorialColumns() > 0)) {

                    bnf.add(currentGene);
                    currentGene.add(geneType);
                    currentGene.add(geneName);
                    //      currentGene.add(geneName);
                    List currentPosList = new ArrayList();
                    currentGene.add(currentPosList);
                    while (pos.hasMoreTokens()) {
                        StringTokenizer arg = new StringTokenizer((String) pos.nextToken(
                                POSDELIM), ",");
                        List currentPos = new ArrayList();
                        currentPosList.add(currentPos);
                        while (arg.hasMoreTokens()) {
                            String name = arg.nextToken();
                            Node gen =  (Node) (Class.forName(Options.GENE_PACKAGE + "." + name).newInstance());                            
                            if (gen==null
                                    ||!( gen instanceof IContinuousOperator || gen instanceof ICategoricalOperator) 
                                    || (gen instanceof IContinuousOperator && environment.getData().getNrOfContinousColumns() > 0) 
                                    || (gen instanceof ICategoricalOperator && environment.getData().getNrOfCategorialColumns() > 0)) {
                                currentPos.add(name);
                            }
                        }
                    } //while
                }
            } catch (Exception e) {
                ErrorManager.getInstance().reportError(e);
                ErrorManager.getInstance().reportError(new BNFException("Gene :" + geneName + " does not exist"));
            }
        } //end for

        bnfFile.close();

        return ops;
    }

    private void parseBNF() throws BNFException {
        parse();
        terminals = new ArrayList();
        sTerminals = new ArrayList();
        functions = new ArrayList();
        sFunctions = new ArrayList();
        termfuncs = new ArrayList();
        List geneList = null;
        ;
        //Det forsta elementet or geneType(func/term) det andra GenName(0,1,2) det
        // tredje en lista av kompatibla gener
        for (Iterator iter = bnf.iterator(); iter.hasNext();) {
            geneList = (List) iter.next();
            if (isFunction((String) geneList.get(Options.GENE_NAME))) {

                functions.add(geneList.get(Options.GENE_NAME));
            } else if (isTerminalfunction((String) geneList.get(Options.GENE_NAME))) {
                termfuncs.add(geneList.get(Options.GENE_NAME));
            } else{
                terminals.add(geneList.get(Options.GENE_NAME));
            }
        }
    }

    public void parse() throws BNFException {
        for (Iterator iter = bnf.iterator(); iter.hasNext();) {
            List geneList = (List) iter.next();
            List posLists = (List) geneList.get(Options.COMPATIBLE_GENES);
            for (Iterator j = posLists.iterator(); j.hasNext();) {
                List posList = (List) j.next();
                for (Iterator k = posList.iterator(); k.hasNext();) {
                    String geneName = (String) k.next();
                    if (!existsGene(geneName)) {
                        throw new BNFException("The BNF is not consistent");
                    }
                }
            }
        }
    }

    public String[] getHeadFunctions() {
        List headList = (List) bnf.get(Options.HEAD);
        List posList = (List) headList.get(Options.COMPATIBLE_GENES);
        List pos = (List) posList.get(Options.HEAD);
        String[] tmp = new String[pos.size()];
        int j = 0;
        for (Iterator i = pos.iterator(); i.hasNext(); j++) {
            tmp[j] = (String) i.next();
        }

        return tmp;
    }

    //Kontrollerar om genens typ finns i bnf
    private boolean existsGene(String geneName) {
        if (bnf != null) {
            for (Iterator i = bnf.iterator(); i.hasNext();) {
                List item = (List) i.next();
                if (geneName.equals(item.get(Options.GENE_NAME))) {
                    ;
                }
                return true;
            }
        }
        return false;
    }

    private boolean isFunction(String o) throws BNFException {
        List gene = getGeneList(o);
        String type = (String) gene.get(Options.GENE_TYPE);
        if (type.equalsIgnoreCase(FUNCTION_MARKER)) {
            return true;
        }
        return false;
    }

    private boolean isTerminalPredictor(String o) throws BNFException {
        List gene = getGeneList(o);
        String type = (String) gene.get(Options.GENE_TYPE);
        if (type.equalsIgnoreCase(TERMINAL_PREDICTOR_MARKER)) {
            return true;
        }
        return false;
    }

    private boolean isTerminalfunction(String o) throws BNFException {
        List gene = getGeneList(o);
        String type = (String) gene.get(Options.GENE_TYPE);
        if (type.equalsIgnoreCase(TERMFUNC_MARKER)) {
            return true;
        }
        return false;
    }

    private boolean isTerminal(String o) throws BNFException {
        List gene = getGeneList(o);
        String type = (String) gene.get(Options.GENE_TYPE);
        if (type.equalsIgnoreCase(TERMINAL_MARKER)) {
            return true;
        }
        return false;
    }

    public String intToStr(int nr) {
        return intTostr[nr];
    }

    /**
     *
     * @param geneName Det tal som beskriver genen
     * @return hela den lista av listor som beskriver hela genen
     * @throws BNFException
     */
    public List getGeneList(String geneName) throws BNFException {
        for (Iterator i = bnf.iterator(); i.hasNext();) {
            List item = (List) i.next();
            if (geneName.equals(item.get(Options.GENE_NAME))) {
                return item;
            }
        }
        throw new BNFException("Gene " + geneName + " doesn't exist");
    }

    public List getGenePositions(Node gene) throws BNFException {
        List geneList = getGeneList(gene.getNodeName());
        return (List) geneList.get(Options.COMPATIBLE_GENES);
    }

    public void createGP(GP gp, int creationType, int maxDepth) throws BNFException, GeneException {
        gp.setOptions(ops);
//        gp.setFitnessFunctionFactory(getFitnessFactory());
        switch (creationType) {
            case Options.GROW:
                createGrowGP(gp, maxDepth);
                break;
            case Options.VARIABLE:
                createVariableGP(gp, maxDepth);
                break;
        }
    }

    public String toString() {
        String tmp = "";
        for (Iterator i = bnf.iterator(); i.hasNext();) {
            List geneList = (List) i.next();
            System.out.print(geneList.get(Options.GENE_TYPE) + ", ");
            System.out.print(geneList.get(Options.GENE_NAME) + " : ");
            List posList = (List) geneList.get(Options.COMPATIBLE_GENES);
            for (Iterator j = posList.iterator(); j.hasNext();) {
                for (Iterator k = ((List) j.next()).iterator(); k.hasNext();) {
                    System.out.print(k.next() + " ");
                }
                System.out.print("; ");
            }
            System.out.println("");
        }
        return tmp;
    }

    private void createVariableGP(GP gp, int maxDepth) throws GeneException, BNFException {
        Node head = createGene(getContentRnd(this.getHeadFunctions()));
        gp.setHead(head);
        createVariableGene(head, maxDepth);
    }

    public void createVariableGene(Node gene, int depth) throws GeneException, BNFException {
        Node[] children = gene.getChildren();
        Node tmp;
        String geneName;

        if (gene != null && (depth == 0 || isTerminalfunction(gene.getNodeName()))) {
            if (children != null) {
                for (int i = 0; i < children.length; i++) {
                    int error = 0;
                    //  printGene(gene,i);

                    do {

                        geneName = getContentRnd(getCompatibleGenes(gene, i));
                        if (error++ > 1000) {
                            throw new BNFException(
                                    "Fel i BNF Terminal saknas till funktionen:" + gene + " tried " + geneName);
                        }
                    } while (!isTerminal(geneName));
                    children[i] = createGene(geneName);
                }
            }
        } /**Volj alltid en funktion/terminal om djupet or storre on 1. Volj
         * volj ockso alltid en funktoin/terminal om djupet or storrre on 0 och vi
         * inte anvonder oss av terminalfunctions.
         */
        else if ((depth > 1 && children != null) || (depth > 0 && termfuncs.size() == 0 && children != null)) {
            for (int i = 0; i < children.length; i++) {
                geneName = getContentRnd(getCompatibleGenes(gene, i));
                children[i] = createGene(geneName);
            }
            for (int i = 0; i < children.length; i++) {
                createVariableGene(children[i], depth - 1);
            }
        } //VI or po djup 1 och vi har terminalfunctions
        else if (depth == 1 && children != null) {
            for (int i = 0; i < children.length; i++) {
                //children[i] = createGene(getContentRnd(getArray(termfuncs)));
                int tries = 0;
                do {
                    tries++;
                    geneName = getContentRnd(getCompatibleGenes(gene, i));
                } while (!isTerminalfunction(geneName) && tries < 100);

                if (tries >= 100) {
                    tries = 0;
                    do {
                        tries++;
                        geneName = getContentRnd(getCompatibleGenes(gene, i));
                    } while (!isTerminal(geneName) && tries < 100);
                }

                children[i] = createGene(geneName);
            }

            for (int i = 0; i < children.length; i++) {
                createVariableGene(children[i], depth - 1);
            }
        }

        gene.setChildren(children);
    }

    private void createGrowGP(GP gp, int depth) throws BNFException {
        Node head = createGene(getContentRnd(this.getHeadFunctions()));
        gp.setHead(head);
        createGrowGene(head, depth);

    }

    private void createGrowGene(Node gene, int depth) throws BNFException {
        Node[] children = gene.getChildren();
        Node tmp;
        String geneName;

        /**Volj alltid en funktion om djupet or storre on 1. Volj
         * volj ockso alltid en funktion om djupet or storrre on 0 och vi
         * inte anvonder oss av terminalfunctions.
         */
        if ((depth > 2 && children != null) || (depth > 0 && termfuncs.size() == 0 && children != null)) {
            for (int i = 0; i < children.length; i++) {
                int tries = 0;
                do {
                    geneName = getContentRnd(getCompatibleGenes(gene, i));
                    tries++;
                } while (!isFunction(geneName) && tries < 100);
                children[i] = createGene(geneName);
            }
            for (int i = 0; i < children.length; i++) {
                createGrowGene(children[i], depth - 1);
            }
        } //VI or po djup 1 och vi har terminalfunctions
        else if (depth == 2 && children != null) {
            for (int i = 0; i < children.length; i++) {
                int tries = 0;
                do {
                    tries++;
                    geneName = getContentRnd(getCompatibleGenes(gene, i));
                } while (!isTerminalfunction(geneName) && tries < 100);

                if (tries >= 100) {
                    tries = 0;
                    do {
                        tries++;
                        geneName = getContentRnd(getCompatibleGenes(gene, i));
                    } while (!isTerminal(geneName) && tries < 100);
                }
                children[i] = createGene(geneName);
            }
            for (int i = 0; i < children.length; i++) {
                createGrowGene(children[i], depth - 1);
            }
        } //Vi or po djup 0 volj en kompatibel terminal
        else if (children != null) {
            for (int i = 0; i < children.length; i++) {
                int error = 0;
                do {
                    geneName = getContentRnd(getCompatibleGenes(gene, i));

                    if (error++ > 1000) {
                        throw new BNFException("Fel i BNF Terminal saknas till funktionen" + gene + " i position " + i + " tried " + geneName);
                    }
                } while (!isTerminal(geneName));
                children[i] = createGene(geneName);
            }
        }
        gene.setChildren(children);
    }

    public String[] getCompatibleGenes(Node gene, int pos) throws BNFException {
        List geneList = getGeneList(gene.getNodeName());
        List posLists = (List) geneList.get(Options.COMPATIBLE_GENES);
        List posList = (List) posLists.get(pos);
        String[] tmp = new String[posList.size()];
        int j = 0;
        for (Iterator i = posList.iterator(); i.hasNext(); j++) {
            tmp[j] = (String) i.next();
        }
        return tmp;
    }

    private String getContentRnd(String[] geneTypes) {
        int index = Options.rnd.nextInt(geneTypes.length);
        return geneTypes[index];
    }

    /**
     * @return the predictorClass
     */
    public String getPredictorClass() {
        return predictorClass;
    }

    /**
     * @param predictorClass the predictorClass to set
     */
    public void setPredictorClass(String predictorClass) {
        this.predictorClass = predictorClass;
    }
   
}
